/**
 * @file crud
 * @author liaoxuezhi@baidu.com
 */
/* eslint fecs-indent: [0, "space", 2, 2] */

import * as React from 'react';
import {
  Component,
  PropTypes
} from 'react';
import * as cx from 'classnames';
import {findDOMNode} from 'react-dom';
import {Renderer, render, buildLink, normalizeLink, filter, evalExpression} from '../renderer';
import reducer from './reducer';
import * as assign from 'object-assign';
import * as actions from './actions';
import {stopNProgress} from '../../actions/asist';
import * as pick from 'lodash/pick';
import * as find from 'lodash/find';
import * as omit from 'lodash/omit';
import * as forEach from 'lodash/forEach';
import {push, replace, goBack} from 'react-router-redux';
import {confirm, alert} from '../../widgets/alert/alert';
import OnExpression from '../hoc/on-expression';
import * as schema from './crud-schema';
import '../dialog/dialog.jsx';
import './field/operation';
import './field/checkbox';
import './field/drag-bar';
import './field/list';
import './field';
import './crud.scss';

export const clean = data => {
  const cleaned = {};
  Object.keys(data).forEach(key => key[0] !== '$' && (cleaned[key] = data[key]));
  return cleaned;
};

export const diff = (pristine, data) => {
  const diffed = {};
  Object.keys(data).forEach(key => key[0] !== '$' && data[key] !== pristine[key] && (diffed[key] = data[key]))
  return diffed;
};

const filterDataToParams = (values, ignoreCountField = true, ignoreObject = true) => {
  let filtered = {};
  values && Object.keys(values).forEach(key => {
    if (values[key] && typeof values[key] === 'object' && ignoreObject) {
      return;
    } else if (ignoreCountField && key === 'count') {
      return;
    }

    filtered[key] = values[key];
  });
  return filtered
};

export class Crud extends Component {
  static propTypes = {
    name: PropTypes.string,
    title: PropTypes.string,
    className: PropTypes.string,
    filter: PropTypes.object,
    api: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.shape({
        url: PropTypes.string.isRequired,
        method: PropTypes.string,
        data: PropTypes.object,
        headers: PropTypes.object
      })
    ]).isRequired,
    messages: PropTypes.shape({
      fetchFailed: PropTypes.string,
      quickSaveFailed: PropTypes.string,
      quickSaveSuccess: PropTypes.string,
      saveOrderFailed: PropTypes.string,
      saveOrderSuccess: PropTypes.string
    }),

    locals: PropTypes.object,
    location: PropTypes.object,
    orderField: PropTypes.string,
    primaryField: PropTypes.string,

    // 是否同步更新浏览器地址栏.
    syncLocation: PropTypes.bool.isRequired,
    stopNProgress: PropTypes.func,
    sendNotify: PropTypes.func.isRequired,
    push: PropTypes.func.isRequired,
    goBack: PropTypes.func.isRequired,
    replace: PropTypes.func.isRequired,

    // required actions
    fetchData: PropTypes.func.isRequired,
    quickSave: PropTypes.func.isRequired,
    saveOrder: PropTypes.func.isRequired,
    doAction: PropTypes.func.isRequired,
    doBulkAction: PropTypes.func.isRequired,

    pageField: PropTypes.string,
    perPageField: PropTypes.string,

    mode: PropTypes.oneOf(['table', 'grid', 'list']),

    initFetch: PropTypes.bool,
    clearCache: PropTypes.bool,
    footable: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.shape({
        expandFirst: PropTypes.bool,
        expandAll: PropTypes.bool,
      })
    ])
  };

  static defaultProps = {
    syncLocation: true,
    pageField: 'page',
    perPageField: 'perPage',
    directionField: 'direction',
    messages: {},
    mode: 'table',
    locals: {},
    primaryField: 'id',
    data: {},
    initFetch: true,
    clearCache: false,
    footable: false,
    hideQuickSaveBtn: false,
    interval: 0
  };

  static contextTypes = {
    scoped: PropTypes.object,
    store: PropTypes.object
  };

  timer = null;
  constructor(props) {
    super(props);

    const params = assign({}, props.defaultParams || {}, props.location && props.location.query || {});
    const data = assign({}, props.state.data, props.data, params);
    this.state = {
      params: params,
      action: null,
      dialog: null,
      ctxData: null,
      data: data,
      locals: assign({}, props.locals, data)
    };

    this.handleOrderByToggle = this.handleOrderByToggle.bind(this);
    this.handlePerPageChange = this.handlePerPageChange.bind(this);
    this.handlePageChange = this.handlePageChange.bind(this);
    this.handleAction = this.handleAction.bind(this);
    this.handleBulkAction = this.handleBulkAction.bind(this);
    this.handleQuickSave = this.handleQuickSave.bind(this);
    this.handleSaveOrder = this.handleSaveOrder.bind(this);
    this.handleDialogRejected = this.handleDialogRejected.bind(this);
    this.handleDialogResolved = this.handleDialogResolved.bind(this);
    this.handleDialogHide = this.handleDialogHide.bind(this);
    // this.handleFilterChange = this.handleFilterChange.bind(this);

    this._search = this._search.bind(this);
    this.search = this.search.bind(this);
    this.filterInit = this.filterInit.bind(this);
    this.receive = this.receive.bind(this);
    this.close = this.close.bind(this);
    this.reload = this.reload.bind(this);
    this.crudRef = this.crudRef.bind(this);
    this.reSearch = this.reSearch.bind(this);
  }

  componentWillMount() {
    const scoped = this.context.scoped;
    scoped && scoped.registerComponent(this);
  }

  componentDidMount() {
    const {
      filter,
      crudRef,
      api,
      initFetch
    } = this.props;
    crudRef && crudRef(this);
    if (!filter && api) {
      initFetch !== false && this._search();
    }
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;
    const state = nextProps.state;
    const sendNotify = nextProps.sendNotify;
    const messages = nextProps.messages;

    // 当有其他保存操作出发的时候会执行。
    if (!props.state.invalid && nextProps.state.invalid && !nextProps.state.error) {
      this._search();
    } else if (props.location && props.location.search !== nextProps.location.search || props.defaultParams !== nextProps.defaultParams) {
      if (nextProps.location.action !== 'REPLACE') {
        const params = assign({}, nextProps.defaultParams || {}, nextProps.location && nextProps.location.query || {});

        this.setState({
          params: params,
          data: assign({}, this.state.data, params)
        }, this._search);
      }
    } else if (props.data !== nextProps.data || props.state.data !== nextProps.state.data) {
      const data = assign({}, this.state.params, nextProps.data, nextProps.state.data);
      this.setState({
        data: data,
        params: assign({}, this.state.params, filterDataToParams(nextProps.state.data)),
        locals: assign({}, nextProps.locals, data)
      });
    }

    // quick save 提示
    if (props.state.quickSave.saving && !nextProps.state.quickSave.saving) {
      const quickSave = state.quickSave;

      if (quickSave.invalid) {
        sendNotify({
          message: messages.quickSaveFailed || (quickSave.error instanceof Error ? quickSave.error.message : quickSave.message) || '快速保存失败',
          level: 'error'
        });
      } else {
        sendNotify({
          message: messages.quickSaveSuccess || quickSave.message || '快速保存成功',
          level: 'success'
        });

        nextProps.reload && this.context.scoped && this.context.scoped.reload(nextProps.reload);
      }
    }

    // save order 提示
    if (props.state.saveOrder.saving && !nextProps.state.saveOrder.saving) {
      const saveOrder = state.saveOrder;

      if (saveOrder.invalid) {
        sendNotify({
          message: messages.saveOrderFailed || (saveOrder.error instanceof Error ? saveOrder.error.message : saveOrder.message) || '顺序保存失败',
          level: 'error'
        });
      } else {
        sendNotify({
          message: messages.saveOrderSuccess || saveOrder.message || '顺序保存成功',
          level: 'success'
        });

        nextProps.reload && this.context.scoped && this.context.scoped.reload(nextProps.reload);
      }
    }

    const action = this.state.action;

    // bulkAction 提示
    if (action && props.state.bulkAction.saving && !nextProps.state.bulkAction.saving) {
      const bulkAction = state.bulkAction;

      if (bulkAction.invalid) {
        sendNotify({
          message: action.messages && action.messages.failed || (bulkAction.error instanceof Error ? bulkAction.error.message : bulkAction.message) || '批量操作失败',
          level: 'error'
        });
      } else {
        sendNotify({
          message: action.messages && action.messages.success || bulkAction.message || '批量操作成功',
          level: 'success'
        });
      }

      action.reload && this.context.scoped && this.context.scoped.reload(action.reload);
      action.feedback && this.handleFeedback(action.feedback, assign({}, this.state.data, state.action.data));
    }

    // action 提示
    if (action && props.state.action.saving && !nextProps.state.action.saving) {
      const actionState = state.action;

      if (actionState.invalid) {
        sendNotify({
          message: action.messages && action.messages.failed || (actionState.error instanceof Error ? actionState.error.message : actionState.message) || '操作失败',
          level: 'error'
        });
      } else {
        sendNotify({
          message: action.messages && action.messages.success || actionState.message || '操作成功',
          level: 'success'
        });

        action.reload && this.context.scoped && this.context.scoped.reload(action.reload);
        action.feedback && this.handleFeedback(action.feedback, assign({}, this.state.data, state.action.data));
      }
    }
  }

  componentWillUnmount() {
    const {
      clearCrud,
      scopedKey,
      clearCache
    } = this.props;

    clearTimeout(this.timer);
    const scoped = this.context.scoped;
    scoped && scoped.unRegisterComponent(this);
    clearCache && clearCrud(scopedKey);
  }

  // handleFilterChange(values) {
  //   const params = assign({}, this.state.params, values);
  //   this.setState({
  //     params: params,
  //     data: assign({}, this.state.data, params)
  //   }, this.props.filter.submitOnChange ? this._search : null);
  // }

  receive(values) {
    this.search(values);
  }

  filterInit(values) {
    const params = assign({}, this.state.params, values);
    const data = assign({}, this.state.data, params);
    const initFetch = this.props.initFetchOn ? evalExpression(this.props.initFetchOn, data) : this.props.initFetch;
    this.setState({
      params: params,
      data: data
    }, initFetch ? this._search : null);
  }

  search(values, dontResetPage) {
    const {
      syncLocation
    } = this.props;

    if (values) {
      let params = assign({}, this.state.params, filterDataToParams(values, false, syncLocation), dontResetPage ? null : {
        [this.props.pageField]: 1
      });
      this.setState({
        params: params,
        data: assign({}, this.state.data, params)
      }, this._search);
    } else {
      this._search();
    }
  }

  reSearch() {
    this.form ? this.search(this.form.getModel(), true) : this._search();
  }

  _search() {
    const {
      scopedKey,
      api,
      fetchData,
      syncLocation,
      replace,
      location,
      locals,
      perPageField,
      pageField,
      directionField,
      messages,
      sendNotify,
      state
    } = this.props;
    let params = this.state.params;

    clearTimeout(this.timer);

    if (state.fetching) {
      return;
    }

    fetchData(scopedKey, api, assign({}, params), {
      locals: assign({}, locals, this.state.data, {
        perPage: params[perPageField] || 10,
        page: params[pageField] || 1,
        direction: params[directionField] || 'forward'
      }),
      perPage: params[perPageField] || 10,
      page: params[pageField] || 1,
      direction: params[directionField] || 'forward'
    }).then(action => {
      if (action.error) {
        const error = action.payload;
        const message = messages.fetchFailed || error instanceof Error && error.message || '获取数据失败';
        return sendNotify({
          message,
          level: 'error'
        });
      }

      if (this.props.interval) {
        this.timer = setTimeout(this.reSearch, Math.max(3000, this.props.interval));
      }
    });

    if (syncLocation) {
      let query = {};
      let originQuery = location && location.query || {};
      forEach(params, (value, key)=>{typeof originQuery[key] === 'undefined' && !value || (query[key] = params[key])});

      replace(assign({}, location, {
        query
      }));
    }
  }

  handleOrderByToggle(name) {
    const params = this.state.params;
    let params2 = assign({}, params, {
      orderBy: name,
      orderDir: params.orderBy === name ? params.orderDir === 'asc' ? 'desc' : 'asc' : 'asc'
    });
    this.setState({
      params: params2,
      data: assign({}, this.state.data, params2)
    }, this._search);
  }

  jumpToTop() {
    const el = findDOMNode(this).querySelector('.crud-body');
    el && el.scrollIntoView();
    const scrolledY = window.scrollY;

    if(scrolledY){
      window.scroll(0, scrolledY - 60);
    }
  }

  handlePerPageChange(perPage) {
    const params = this.state.params;
    let params2 = assign({}, params, {
      [this.props.pageField]: 1,
      [this.props.perPageField]: perPage
    });
    this.setState({
      params: params2,
      data: assign({}, this.state.data, params2)
    }, this._search);

    this.jumpToTop();
  }

  handlePageChange(page, direction = '', otherParams = null) {
    const params = this.state.params;
    direction = typeof direction === 'string' ? direction : '';
    const data = {
      [this.props.pageField]: page
    };

    direction && (data[this.props.directionField] = direction);
    const params2 = assign({}, params, otherParams, data);
    this.setState({
      params: params2,
      data: assign({}, this.state.data, params2)
    }, this._search);

    this.jumpToTop();
  }

  // alias for handleAction
  doAction(action, data = {}) {
    this.handleAction(action, data);
  }

  handleAction(action, raw = this.state.data) {
    const data = assign({}, omit(this.state.data, ['rows']), clean(raw));
    const onRequestAction = this.props.onRequestAction;

    if (action.type === 'submit' || action.type === 'reset') {
      this.form && this.form[action.type]();
      return;
    }

    if (action.actionType === 'dialog') {
      data.hasNext = false;

      const rest = this.state.data.rows && this.state.data.rows.slice(raw.$index + 1);
      const next = rest && find(rest, item => action.nextCondition ? evalExpression(action.nextCondition, item) : true);
      data.hasNext = !!next;

      return this.setState({
        action,
        dialog: action.dialog || action.action,
        ctxData: Array.isArray(data) ? data : assign({}, data),
        ctxRaw: raw
      });
    } else if (action.actionType === 'ajax' || action.actionType === 'open') {
      const {
        scopedKey,
        doAction
      } = this.props;

      let fn = () => this.setState({action: action}, () => doAction(scopedKey, action.api || action.action, data, assign({
        locals: this.props.locals
      }, raw)));

      if (action.actionType === 'open') {
        fn = () => window.open(buildLink(action.href || action.action, data));
      }

      return action.confirmText ? confirm(filter(action.confirmText, data)).then(fn) : fn();
    } else if (action.actionType === 'link') {
      const push = this.props.push;
      const goBack = this.props.goBack;
      let fn = () => action.link === 'goBack' || action.action === 'goBack' ? goBack() : push(normalizeLink(filter(action.link || action.action, data)));
      return action.confirmText ? confirm(filter(action.confirmText, data)).then(fn) : fn();
    } else if (action.actionType === 'url') {
      let fn = action.blank === false ? () => location.href = buildLink(action.url || action.action, data) : () => window.open(buildLink(action.url || action.action, data));
      return action.confirmText ? confirm(filter(action.confirmText, data)).then(fn) : fn();
    } else if (onRequestAction) {
      return onRequestAction(action, data);
    }

    alert(`不能识别的 action ${action}: ${JSON.stringify(raw, null, 2)}`);
  }

  handleFeedback(feedback, data) {
    this.handleAction({
      actionType: 'dialog',
      dialog: feedback
    }, data);
  }

  handleBulkAction(actionType, rows, action, uncheckedRows) {
    const primaryField = this.props.primaryField;
    if (actionType === 'dialog') {
      const data = assign({}, rows[0]);
      data.rows = rows.map(clean);
      data.uncheckRows = uncheckedRows.map(clean);

      return this.setState({
        action,
        ctxData: Array.isArray(data) ? data : assign({}, data),
        ctxRaw: rows,
        dialog: assign({}, action.dialog, {
          optimistic: action.optimistic
        })
      });
    } else if (actionType === 'ajax' || actionType === 'url') {
      const value = assign({}, omit(this.state.data, ['rows']), {
        rows: rows.map(clean)
      });

      if (rows[0] && rows[0][primaryField]) {
        value.ids = rows.map(item => item[primaryField]).join(',');
      }

      const {
        scopedKey,
        doBulkAction
      } = this.props;
      const newState = {action: action};

      if (action.optimistic) {
        const newRows = this.state.data.rows.concat();
        rows.reverse().forEach(({$index}) => newRows.splice($index, 1));
        newState.data = assign({}, this.state.data, {rows: newRows});
        this.props.stopNProgress();
        this.props.sendNotify({
          message: '操作已在进行中。。。',
          level: 'success'
        })
      }

      let fn = () => this.setState(newState, () => doBulkAction(scopedKey, action.api || action.action, value, this.props.locals));

      if (actionType === 'open' || actionType === 'url') {
        fn = () => window.open(buildLink(action.href || action.url || action.action, value));
      }

      return action.confirmText ? confirm(filter(action.confirmText, rows)).then(fn) : fn();
    }

    alert(`不能识别的 BulkAction ${action}: ${JSON.stringify(rows, null, 2)}`);
  }

  handleQuickSave(rows) {
    let {
      scopedKey,
      quickSave,
      quickSaveApi,
      quickSaveItemApi,
      sendNotify,
    } = this.props;

    if (!quickSaveApi && !quickSaveItemApi) {
      return sendNotify({
        message: 'quickSaveApi 或者 quickSaveItemApi 没有指定, 不能保存.',
        level: 'error'
      });
    }

    const primaryField = this.props.primaryField;
    let api = quickSaveApi;
    let data = rows.map(item => rows[0][primaryField] ? assign({
      id: item[primaryField]
    }, diff(item.$pristine, item)) : clean(item));

    if (rows.length === 1 && rows[0].$single && quickSaveItemApi) {
      data = data[0];
      api = quickSaveItemApi;
    }

    quickSave(scopedKey, api, data, {
      rows,
      locals: assign({}, this.props.locals, this.state.data)
    });
  }

  handleSaveOrder(rows) {
    const {
      scopedKey,
      saveOrder,
      saveOrderApi,
      sendNotify,
      orderField,
      state,
      locals,
      primaryField
    } = this.props;

    if (!saveOrderApi) {
      return sendNotify({
        message: 'saveOrderApi 没有指定, 不能保存.',
        level: 'error'
      });
    }

    if (orderField) {
      const start = (state.pager.currentPage - 1) * state.pager.perPage || 0;
      rows.forEach((item, key) => item[orderField] = start + key + 1);
    }

    const model = assign({}, locals, this.state.data, {
      insertAfter: {}
    });

    let insertAfter;
    let insertBefore;
    const holding = [];
    const hasIdField = rows[0] && rows[0][primaryField];

    hasIdField || (model.idMap = {});

    rows.forEach(item => {
      if (item.$modified) {
        if (insertAfter) {
          model.insertAfter[insertAfter.$id] = model.insertAfter[insertAfter.$id] || [];
          hasIdField || (model.idMap[insertAfter.$id] = clean(insertAfter));
          model.insertAfter[insertAfter.$id].push(hasIdField ? item[primaryField] : clean(item));
        } else {
          holding.push(item);
        }
      } else {
        insertAfter = item;
        insertBefore = insertBefore || item;
      }
    });

    if (insertBefore && holding.length) {
      hasIdField || (model.idMap[insertBefore.$id] = clean(insertBefore));
      model.insertBefore = {};
      model.insertBefore[insertBefore.$id] = holding.map(item => hasIdField ? item[primaryField] : clean(item));
    } else if (holding.length) {
      hasIdField || (model.idMap[holding[0].$id] = clean(holding[0]));
      model.insertAfter[holding[0].$id] = holding.slice(1).map(item => hasIdField ? item[primaryField] : clean(item));
    }

    model.rows = rows.map(clean);
    hasIdField && (model.ids = rows.map(item => item[primaryField]).join(','));
    hasIdField && orderField && (model.order = rows.map(item => pick(item, [primaryField, orderField])));

    saveOrder(scopedKey, saveOrderApi, model, {
      rows,
      locals: this.props.locals
    });
  }

  handleDialogHide() {
    const ctxRaw = this.state.ctxRaw;

    if (!ctxRaw) {
      return;
    }

    const newRows = this.state.data.rows.concat();
    ctxRaw.reverse().forEach(({$index}) => newRows.splice($index, 1));
    this.setState({
      data: assign({}, this.state.data, {rows: newRows})
    });
    this.props.stopNProgress();
    this.props.sendNotify({
      message: '操作已在进行中。。。',
      level: 'success'
    })
  }

  handleDialogRejected(trigger, dialogProps) {
    const raw = this.state.ctxRaw;

    this.close(() => {
      if (!trigger) {
        return;
      }

      const action = this.state.action;

      if (trigger && trigger.actionType === 'next' && this.crud) {
        const crud = this.crud;
        const rest = crud.state.rows && crud.state.rows.slice(raw.$index + 1);
        const next = rest && find(rest, item => action.nextCondition ? evalExpression(action.nextCondition, item) : true);

        if (next) {
          return this.handleAction(action, next);
        }
      }
    });
  }

  handleDialogResolved(values, trigger, dialogProps) {
    const raw = this.state.ctxRaw;

    this.close(() => {
      if (!values || dialogProps && dialogProps.optimistic) {
        return;
      }

      this._search();

      const action = this.state.action;

      if (action.reload) {
        this.context.scoped && this.context.scoped.reload(action.reload);
      } else if (action.feedback) {
        this.handleFeedback(action.feedback, this.state.data);
      } else if (trigger && trigger.actionType === 'next' && this.crud) {
        const crud = this.crud;
        const rest = crud.state.rows && crud.state.rows.slice(raw.$index + 1);
        const next = rest && find(rest, item => action.nextCondition ? evalExpression(action.nextCondition, item) : true);

        if (next) {
          return this.handleAction(action, next);
        }
      }
    });
  }

  reload() {
    this._search();
  }

  close(fn) {
    this.setState({
      dialog: null,
      ctxData: null,
      ctxRaw: null
    }, typeof fn === 'function' ? fn : null);
  }

  crudRef(crud) {
    this.crud = crud;
  }

  render() {
    const {
      className,
      filter,
      title,
      state,
      sendNotify,
      mode,
      locals,
      primaryField,
      innerClassName,
      $path
    } = this.props;

    const loading = state.fetching || state.saving || state.quickSave.saving
      || state.action.saving || state.saveOrder.saving || state.bulkAction.saving;
    const params = this.state.params;
    const {dialog, ctxData, data} = this.state;
    const childLocals = assign({}, locals, data);

    data && data.rows && !Array.isArray(data.rows) && (data.rows = [data.rows]);

    return (
      <div className={cx("crud-wrapper", className)}>
        {filter ? render(`${$path}/filter`, filter, {
          type: filter.type || 'filter',
          disabled: loading,
          data: params,
          locals: childLocals,
          errors: state.error && state.error.type === 'ValidationError' ? state.error.detail
            : state.action.error && state.action.error.type === 'ValidationError' ? state.action.error.detail : null,
          formRef: form => this.form = form,
          // onChange: this.handleFilterChange,
          onRequestAction: this.handleAction,
          onInit: this.filterInit,
          onSubmit: this.search
        }) : null}

        {render(`${$path}/${mode}`, {
          title
        }, assign({}, this.props, {
          panelClassName: this.props.panelClassName,
          crudRef: this.crudRef,
          className: cx('crud-body', innerClassName),
          loading,
          data: data,
          params: this.state.params,
          locals: childLocals,
          rows: data && data.rows || [],
          primaryField,

          currentPage: state.pager.currentPage, // 当前第几页
          lastPage: state.pager.lastPage, // 总共有多少页
          perPage: state.pager.perPage, // 每页多少个
          total: state.pager.total, // 总共多少个
          hasMore: state.pager.hasMore, // 有时并不给出 count, 不要页码, 只管能不能下一页.

          orderBy: params.orderBy,
          orderDir: params.orderDir,
          onOrderByToggle: this.handleOrderByToggle,
          onNotify: sendNotify,


          onPerPageChange: this.handlePerPageChange,
          onPaginationChange: this.handlePageChange,

          onAction: this.handleAction,
          onRequestAction: this.handleAction,
          onBulkAction: this.handleBulkAction,
          onQuickSave:  this.handleQuickSave,
          onSaveOrder: this.handleSaveOrder,
          onSearch: this.search,
          onInit: this.filterInit
        }))}

        {dialog ? render(`${$path}/form-dialog`, dialog, {
          data: ctxData,
          locals: childLocals,
          sendNotify,
          primaryField,
          onClose: this.close,
          onRejected: this.handleDialogRejected,
          onResolved: this.handleDialogResolved,
          onHide: this.handleDialogHide,
        }) : null}
      </div>
    );
  }
}

@Renderer({
  name: 'crud',
  desc: '通用的增删改查模型',
  test: /(^|\/)crud$/,
  reducer,
  schema
}, null, assign({
  push,
  replace,
  goBack,
  stopNProgress
}, actions))
@OnExpression
export default class CrudRenderer extends Crud {}
